package com.study.springbootsecurity.util;
// 导入 com.google.zxing 包下的 BarcodeFormat 枚举，用于指定条码格式（如：二维码）
import com.google.zxing.BarcodeFormat;
// 导入 com.google.zxing 包下的 EncodeHintType 枚举，用于在编码时传递额外参数
import com.google.zxing.EncodeHintType;
// 导入 com.google.zxing 包下的 MultiFormatWriter 类，一个可以编码多种格式条码的写入器
import com.google.zxing.MultiFormatWriter;
// 导入 com.google.zxing 包下的 WriterException 类，是 Zxing 库在写入（编码）时可能抛出的异常
import com.google.zxing.WriterException;
// 导入 Zxing Java SE 扩展包中的 MatrixToImageConfig 类，用于配置二维码颜色
import com.google.zxing.client.j2se.MatrixToImageConfig;
// 导入 Zxing Java SE 扩展包中的 MatrixToImageWriter 类，用于将 BitMatrix 对象写入图片文件或流
import com.google.zxing.client.j2se.MatrixToImageWriter;
// 导入 com.google.zxing.common 包下的 BitMatrix 类，是二维码的二进制矩阵表示形式
import com.google.zxing.common.BitMatrix;
// 导入 Zxing 二维码包下的 ErrorCorrectionLevel 类，用于指定二维码的容错级别
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
// 导入 Apache Commons Lang3 库中的 StringUtils 类，提供方便的字符串操作（如此处的 isBlank）
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

// 导入 Java AWT 包中的 Color 类，用于表示颜色
import java.awt.Color;
// 导入 Java AWT 包中的 Graphics2D 类，提供更强大的二维图形绘制功能
import java.awt.Graphics2D;
// 导入 Java AWT 包中的 Image 类，是所有图形类的超类
import java.awt.Image;
// 导入 Java AWT 包中的 Shape 接口，表示一个几何形状
import java.awt.Shape;
// 导入 Java AWT 包中的 BasicStroke 类，定义画笔的属性（如粗细）
import java.awt.BasicStroke;
// 导入 Java AWT 几何图形包中的 RoundRectangle2D 类，用于创建圆角矩形
import java.awt.geom.RoundRectangle2D;
// 导入 Java AWT 图像包中的 BufferedImage 类，用于处理内存中的图像数据
import java.awt.image.BufferedImage;
// 导入 Java IO 包中的 File 类，用于操作文件和目录
import java.io.File;
// 导入 Java IO 包中的 IOException 类，是 IO 操作中可能抛出的异常
import java.io.IOException;
// 导入 Java IO 包中的 OutputStream 类，是所有字节输出流的超类
import java.io.OutputStream;
// 导入 Java Util 包中的 HashMap 类，用于创建键值对集合
import java.util.HashMap;
// 导入 Java Util 包中的 Map 接口，用于存储键值对
import java.util.Map;
// 导入 Java ImageIO 包，一个用于读写图片的核心工具类
import javax.imageio.ImageIO;
/**
 * @Description: TODO 生成二维码工具类
 * @author: Huangjianyun
 * @date: 2025-06-20 10:00
 */



/**
 * 二维码生成工具类 (优化版 & 逐行注释版)
 *
 * @author YourName
 * @version 2.1
 */
@Slf4j
public class QrCodeUtil {

    // region: --- 默认常量 --- // 使用 region 折叠代码块，定义所有默认常量

    /**
     * 默认二维码宽度（单位：像素）
     */
    private static final int DEFAULT_WIDTH = 400; // 定义一个私有的、静态的、最终的整型常量，表示默认宽度

    /**
     * 默认二维码高度（单位：像素）
     */
    private static final int DEFAULT_HEIGHT = 400; // 定义一个私有的、静态的、最终的整型常量，表示默认高度

    /**
     * 默认图像类型（例如：png, jpg）
     */
    private static final String DEFAULT_IMAGE_FORMAT = "png"; // 定义一个私有的、静态的、最终的字符串常量，表示默认图片格式

    /**
     * 默认前景色（黑色），使用 32 位 ARGB 格式，0xFF 表示完全不透明
     */
    private static final int DEFAULT_FRONT_COLOR = 0xFF000000; // 定义一个私有的、静态的、最终的整型常量，表示二维码像素颜色

    /**
     * 默认背景色（白色），使用 32 位 ARGB 格式
     */
    private static final int DEFAULT_BACKGROUND_COLOR = 0xFFFFFFFF; // 定义一个私有的、静态的、最终的整型常量，表示二维码背景颜色

    /**
     * 默认二维码边距（单位：像素），值越小，二维码离图片边缘越近
     */
    private static final int DEFAULT_MARGIN = 1; // 定义一个私有的、静态的、最终的整型常量，表示二维码外边距

    /**
     * 默认错误修正级别 (M: ~15% correction)，容错率越高，可被遮挡的面积越大
     */
    private static final ErrorCorrectionLevel DEFAULT_ERROR_CORRECTION = ErrorCorrectionLevel.M; // 定义默认的容错级别为 M

    /**
     * Logo占整个二维码的百分比，例如 20 就是 20%
     */
    private static final int LOGO_PERCENT = 20; // 定义一个私有的、静态的、最终的整型常量，表示 Logo 的大小比例


    /**
     * 根据内容生成二维码到指定文件 (使用默认配置)
     *
     * @param content      二维码内容
     * @param destFile     目标文件
     * @throws IOException      当文件写入失败时抛出
     * @throws WriterException  当 Zxing 编码失败时抛出
     */
    public static void createCodeToFile(String content, File destFile) throws IOException, WriterException { // 定义一个公共静态方法，只接受内容和目标文件
        createCodeToFile(content, null, destFile,null,null); // 调用更详细的重载方法，logoFile 和尺寸都传入 默认值
    }

    /**
     * 根据内容生成带 Logo 的二维码到指定文件 (使用默认配置)
     *
     * @param content      二维码内容
     * @param logoFile     Logo 图片文件
     * @param destFile     目标文件
     * @throws IOException      当文件读写失败时抛出
     * @throws WriterException  当 Zxing 编码失败时抛出
     */
    public static void createCodeToFile(String content, File logoFile, File destFile) throws IOException, WriterException { // 定义一个公共静态方法，接受内容、logo和目标文件
        createCodeToFile(content, logoFile, destFile,null,null); // 调用更详细的重载方法，尺寸传入设置默认值
    }

    /**
     * 根据内容生成带 Logo 的二维码到指定文件 (可自定义尺寸)
     *
     * @param content      二维码内容
     * @param logoFile     Logo 图片文件 (可以为 null，表示不带 Logo)
     * @param destFile     目标文件
     * @param width        宽度 (可以为 null，使用默认值)
     * @param height       高度 (可以为 null，使用默认值)
     * @throws IOException      当文件读写失败时抛出
     * @throws WriterException  当 Zxing 编码失败时抛出
     */
    public static void createCodeToFile(String content, File logoFile, File destFile, Integer width, Integer height) throws IOException, WriterException { // 定义一个最完整的公共静态方法
        if (destFile == null) { // 检查目标文件对象是否为 null
            throw new IllegalArgumentException("目标文件不能为空"); // 如果为 null，则抛出非法参数异常
        } // if 语句结束
        if (!destFile.getParentFile().exists()) { // 检查目标文件的父目录是否存在
            destFile.getParentFile().mkdirs(); // 如果父目录不存在，则创建所有必需的父目录
        } // if 语句结束

        BufferedImage image = createBufferedImage(content, logoFile, width, height); // 调用核心方法生成二维码的 BufferedImage 对象
        ImageIO.write(image, DEFAULT_IMAGE_FORMAT, destFile); // 使用 ImageIO 将生成的图片写入到目标文件中
        log.info("二维码已成功生成到: {}", destFile.getAbsolutePath()); // 在控制台打印成功信息和文件路径
    }

    /**
     * 根据内容生成二维码并写入输出流 (使用默认配置)
     *
     * @param content      二维码内容
     * @param outputStream 输出流，例如 HttpServletResponse 的 getOutputStream()
     * @throws IOException      当流写入失败时抛出
     * @throws WriterException  当 Zxing 编码失败时抛出
     */
    public static void createCodeToOutputStream(String content, OutputStream outputStream) throws IOException, WriterException { // 定义一个公共静态方法，接受内容和输出流
        createCodeToOutputStream(content, null, outputStream, null, null); // 调用更详细的重载方法，logo 和尺寸都传入 null
    }

    /**
     * 根据内容生成带 Logo 的二维码并写入输出流 (使用默认配置)
     *
     * @param content      二维码内容
     * @param logoFile     Logo 图片文件
     * @param outputStream 输出流
     * @throws IOException      当流写入或文件读取失败时抛出
     * @throws WriterException  当 Zxing 编码失败时抛出
     */
    public static void createCodeToOutputStream(String content, File logoFile, OutputStream outputStream) throws IOException, WriterException { // 定义一个公共静态方法，接受内容、logo 和输出流
        createCodeToOutputStream(content, logoFile, outputStream, null, null); // 调用更详细的重_METHOD，尺寸传入 null
    }

    /**
     * 根据内容生成带 Logo 的二维码并写入输出流 (可自定义尺寸)
     *
     * @param content      二维码内容
     * @param logoFile     Logo 图片文件 (可以为 null)
     * @param outputStream 输出流
     * @param width        宽度 (可以为 null，使用默认值)
     * @param height       高度 (可以为 null，使用默认值)
     * @throws IOException      当流写入或文件读取失败时抛出
     * @throws WriterException  当 Zxing 编码失败时抛出
     */
    public static void createCodeToOutputStream(String content, File logoFile, OutputStream outputStream, Integer width, Integer height) throws IOException, WriterException { // 定义最完整的公共静态方法
        BufferedImage image = createBufferedImage(content, logoFile, width, height); // 调用核心方法生成二维码的 BufferedImage 对象
        ImageIO.write(image, DEFAULT_IMAGE_FORMAT, outputStream); // 使用 ImageIO 将生成的图片写入到输出流中
        System.out.println("二维码已成功写入输出流。"); // 在控制台打印成功信息
    }


    /**
     * 核心方法：根据内容和配置生成二维码 BufferedImage 对象
     *
     * @param content  二维码内容
     * @param logoFile Logo 图片文件 (可以为 null)
     * @param width    宽度
     * @param height   高度
     * @return BufferedImage 二维码图像对象
     * @throws WriterException 当 Zxing 编码失败时
     * @throws IOException     当 Logo 文件读取失败时
     */
    private static BufferedImage createBufferedImage(String content, File logoFile, Integer width, Integer height) throws WriterException, IOException { // 定义一个私有静态方法，这是所有生成逻辑的核心
        if (StringUtils.isBlank(content)) { // 使用 StringUtils.isBlank 检查内容是否为 null、空字符串或仅包含空白字符
            throw new IllegalArgumentException("二维码内容不能为空"); // 如果内容为空白，则抛出非法参数异常
        }

        int finalWidth = (width == null || width <= 0) ? DEFAULT_WIDTH : width; // 如果传入的宽度为 null 或小于等于0，则使用默认宽度，否则使用传入值
        int finalHeight = (height == null || height <= 0) ? DEFAULT_HEIGHT : height; // 如果传入的高度为 null 或小于等于0，则使用默认高度，否则使用传入值

        // --- 1. 创建 BitMatrix (位矩阵)，这是二维码的数据结构 ---
        BitMatrix bitMatrix = createBitMatrix(content, finalWidth, finalHeight); // 调用辅助方法生成 BitMatrix

        // --- 2. 创建二维码配置，指定前后景色 ---
        // 参数1：前景色 (二维码有效像素点)； 参数2：背景色 (二维码空白区域)
        MatrixToImageConfig config = new MatrixToImageConfig(DEFAULT_FRONT_COLOR, DEFAULT_BACKGROUND_COLOR); // 创建一个颜色配置对象

        // --- 3. 将 BitMatrix 转换为 BufferedImage，这是内存中的图像对象 ---
        // 相比手动循环像素点，此方法性能更高
        BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix, config); // 使用 Zxing 提供的工具类直接转换，传入位矩阵和颜色配置

        // --- 4. 如果有 Logo，则在生成的二维码图片上嵌入 Logo ---
        if (logoFile != null && logoFile.exists()) { // 检查 logo 文件是否不为 null 并且真实存在
            addLogo(image, logoFile); // 调用 addLogo 方法将 logo 绘制到图片上
        }

        return image; // 返回最终生成的 BufferedImage 对象
    }

    /**
     * 生成二维码的位矩阵 (BitMatrix)
     *
     * @param content 二维码内容
     * @param width   宽度
     * @param height  高度
     * @return BitMatrix 二维码位矩阵
     * @throws WriterException 当 Zxing 编码失败时
     */
    private static BitMatrix createBitMatrix(String content, int width, int height) throws WriterException { // 定义一个私有静态方法，专门用于生成 BitMatrix
        // --- 设置 Zxing 生成二维码的参数 (HINTS)，以 Map 形式存储 ---
        Map<EncodeHintType, Object> hints = new HashMap<>(); // 新建一个 HashMap 对象来存储编码提示

        // 1. 设置字符编码 (Character Set)，确保中文等特殊字符能被正确编码
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 将字符集设置为 UTF-8

        // 2. 设置容错率 (Error Correction)
        // L = ~7%, M = ~15%, Q = ~25%, H = ~30%
        // 容错率越高，二维码能被遮挡的面积越大，但同时信息密度也会降低。通常选择 M。
        hints.put(EncodeHintType.ERROR_CORRECTION, DEFAULT_ERROR_CORRECTION); // 将容错级别设置为预定义的默认值（M）

        // 3. 设置外边距 (Margin)
        // 值越小，二维码距离图片边缘越近。
        hints.put(EncodeHintType.MARGIN, DEFAULT_MARGIN); // 将外边距设置为预定义的默认值（1）

        // --- 使用 MultiFormatWriter 生成 BitMatrix ---
        // 参数1: content, 要编码的内容
        // 参数2: BarcodeFormat.QR_CODE, 编码格式 (二维码)
        // 参数3: width, 图像宽度
        // 参数4: height, 图像高度
        // 参数5: hints, 包含各种配置的 Map
        return new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints); // 创建写入器实例并调用 encode 方法，返回生成的 BitMatrix
    }

    /**
     * 在二维码图片上添加 Logo
     *
     * @param qrCodeImage 二维码图片 BufferedImage 对象
     * @param logoFile    Logo 文件对象
     * @throws IOException 当读取 Logo 文件失败时
     */
    private static void addLogo(BufferedImage qrCodeImage, File logoFile) throws IOException { // 定义一个私有静态方法，用于添加 Logo
        // --- 1. 读取并准备 Logo 图片 ---
        BufferedImage logoImage = ImageIO.read(logoFile); // 使用 ImageIO 读取 logo 文件到内存中，成为一个 BufferedImage 对象
        int logoWidth = logoImage.getWidth(); // 获取 logo 图片的原始宽度
        int logoHeight = logoImage.getHeight(); // 获取 logo 图片的原始高度
        int qrCodeWidth = qrCodeImage.getWidth(); // 获取二维码图片的宽度（也是高度）

        // --- 2. 计算 Logo 在二维码中的尺寸 ---
        // 根据预设的百分比，计算 Logo 在二维码中应有的宽度
        int finalLogoWidth = qrCodeWidth / (100 / LOGO_PERCENT); // 例如二维码400px, 比例20, 则logo宽为400/(100/20)=80px
        // 为了保持 Logo 原始的宽高比，根据新的宽度等比例计算新的高度
        int finalLogoHeight = (int) (((double) finalLogoWidth / logoWidth) * logoHeight); // 使用 double 转型保证计算精度

        // --- 3. 获取二维码的 Graphics2D 对象，用于在图片上绘图 ---
        Graphics2D g = qrCodeImage.createGraphics(); // 从二维码图片对象获取一个 Graphics2D 绘图对象

        // --- 4. 计算 Logo 在二维码中的绘制坐标 (使其居中) ---
        int x = (qrCodeWidth - finalLogoWidth) / 2; // 计算 Logo 左上角的 x 坐标，使其水平居中
        int y = (qrCodeWidth - finalLogoHeight) / 2; // 计算 Logo 左上角的 y 坐标，使其垂直居中

        // --- 5. 在二维码上绘制 Logo 图片 ---
        // 参数1: 要绘制的图片 (logoImage)
        // 参数2,3: 绘制位置的 x,y 坐标
        // 参数4,5: 绘制的宽度和高度 (finalLogoWidth, finalLogoHeight)
        // 参数6: ImageObserver，一个异步更新接口，通常在简单绘制时传 null
        g.drawImage(logoImage, x, y, finalLogoWidth, finalLogoHeight, null); // 执行绘制操作

        // --- 6. 为 Logo 添加一个白色圆角边框，以增强识别性，防止 Logo 破坏二维码 ---
        // 创建一个圆角矩形对象作为边框的形状
        Shape shape = new RoundRectangle2D.Float(x, y, finalLogoWidth, finalLogoHeight, 10, 10); // x,y,宽,高,圆角宽,圆角高
        g.setStroke(new BasicStroke(4f)); // 设置画笔（边框）的粗细为 4 像素
        g.setColor(Color.WHITE); // 设置画笔（边框）的颜色为白色
        g.draw(shape); // 在 Graphics2D 上绘制这个形状（即边框）

        // --- 7. 释放绘图资源 ---
        g.dispose(); // 绘制完成后，必须调用 dispose 方法释放 Graphics2D 对象占用的系统资源
    }


}